#include "Rando/Rando.h"
#include "Rando/Spoiler/Spoiler.h"
#include "2s2h/BenGui/UIWidgets.hpp"
#include "Rando/CheckTracker/CheckTracker.h"
#include "build.h"
#include "2s2h/BenGui/BenMenu.h"

extern "C" {
#include "overlays/actors/ovl_En_Sth/z_en_sth.h"
}

// TODO: This block should come from elsewhere, tied to data in Rando::StaticData::Options
std::unordered_map<int32_t, const char*> logicOptions = {
    { RO_LOGIC_GLITCHLESS, "Glitchless" },
    { RO_LOGIC_NO_LOGIC, "No Logic" },
    { RO_LOGIC_NEARLY_NO_LOGIC, "Nearly No Logic" },
    { RO_LOGIC_VANILLA, "Vanilla" },
};

std::unordered_map<int32_t, const char*> accessDungeonOptions = {
    { RO_ACCESS_DUNGEONS_FORM_AND_SONG, "Requires Transformation & Song" },
    { RO_ACCESS_DUNGEONS_FORM_OR_SONG, "Requires Transformation or Song" },
    { RO_ACCESS_DUNGEONS_FORM_ONLY, "Requires Only Transformation" },
    { RO_ACCESS_DUNGEONS_SONG_ONLY, "Requires Only Song" },
    { RO_ACCESS_DUNGEONS_OPEN, "Open" },
};

std::unordered_map<int32_t, const char*> accessTrialsOptions = {
    { RO_ACCESS_TRIALS_20_MASKS, "2-6-12-20 Masks" },
    { RO_ACCESS_TRIALS_REMAINS, "Requires Associated Remains" },
    { RO_ACCESS_TRIALS_FORMS, "Requires Associated Transformation" },
    { RO_ACCESS_TRIALS_OPEN, "Open" },
};

std::vector<int32_t> incompatibleWithVanilla = {
    RO_SHUFFLE_BOSS_SOULS,
    RO_SHUFFLE_SWIM,
    RO_PLENTIFUL_ITEMS,
};

std::vector<RandoCheckId> checkExclusionList;
bool isExcludedInitialized = false;

namespace BenGui {
extern std::shared_ptr<Rando::CheckTracker::CheckTrackerWindow> mRandoCheckTrackerWindow;
extern std::shared_ptr<Rando::CheckTracker::SettingsWindow> mRandoCheckTrackerSettingsWindow;
extern std::shared_ptr<BenMenu> mBenMenu;
} // namespace BenGui

using namespace BenGui;
using namespace UIWidgets;

extern "C" {
#include "archives/icon_item_24_static/icon_item_24_static_yar.h"
}

void ClearIncompatibleSetting() {
    int32_t currentLogicSetting =
        CVarGetInteger(Rando::StaticData::Options[RO_LOGIC].cvar, Rando::StaticData::Options[RO_LOGIC].defaultValue);
    switch (currentLogicSetting) {
        // Vanilla can't add items without corresponding checks
        case RO_LOGIC_VANILLA:
            CVarClear(Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar);
            CVarClear(Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar);
            CVarClear(Rando::StaticData::Options[RO_SHUFFLE_SWIM].cvar);
            break;
        default:
            break;
    }
}

bool IncompatibleWithLogicSetting(int32_t option) {
    int32_t currentLogicSetting =
        CVarGetInteger(Rando::StaticData::Options[RO_LOGIC].cvar, Rando::StaticData::Options[RO_LOGIC].defaultValue);
    switch (currentLogicSetting) {
        case RO_LOGIC_VANILLA:
            if (std::find(incompatibleWithVanilla.begin(), incompatibleWithVanilla.end(), option) !=
                incompatibleWithVanilla.end()) {
                return true;
            }
            break;
        default:
            break;
    }
    return false;
}

void SortExcludedChecks() {
    std::sort(checkExclusionList.begin(), checkExclusionList.end());
}

void SaveExcludedChecks() {
    std::string excludedString = "";
    SortExcludedChecks();

    for (auto& data : checkExclusionList) {
        excludedString += std::to_string(data);
        excludedString += ",";
    }
    CVarSetString("gRando.ExcludedChecks", excludedString.c_str());
}

void LoadExcludedChecks() {
    std::string checksList = CVarGetString("gRando.ExcludedChecks", "");

    if (checksList != "") {
        std::string word;
        std::istringstream stream(checksList);
        while (std::getline(stream, word, ',')) {
            checkExclusionList.push_back((RandoCheckId)std::stoi(word));
        }
    }
    SortExcludedChecks();
}

static void DrawGeneralTab() {
    ImGui::BeginChild("randoSettings");
    ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 0.5f));
    ImGui::TextWrapped(
        "Explore the menus for various enhancements and time savers; most are not enabled by default in Rando.");
    ImGui::PopStyleColor();

    ImGui::SeparatorText("Seed Generation");
    UIWidgets::CVarCheckbox("Enable Rando (Randomizes new files upon creation)", "gRando.Enabled");

    if (UIWidgets::CVarCombobox("Seed", "gRando.SpoilerFileIndex", Rando::Spoiler::spoilerOptions)) {
        if (CVarGetInteger("gRando.SpoilerFileIndex", 0) == 0) {
            CVarSetString("gRando.SpoilerFile", "");
        } else {
            CVarSetString("gRando.SpoilerFile",
                          Rando::Spoiler::spoilerOptions[CVarGetInteger("gRando.SpoilerFileIndex", 0)].c_str());
        }
    }

    if (CVarGetInteger("gRando.SpoilerFileIndex", 0) == 0) {
        UIWidgets::PushStyleSlider();
        static char seed[256];
        std::string stringSeed = CVarGetString("gRando.InputSeed", "");
        strcpy(seed, stringSeed.c_str());
        ImGui::InputText("##Seed", seed, sizeof(seed), ImGuiInputTextFlags_CallbackAlways,
                         [](ImGuiInputTextCallbackData* data) {
                             CVarSetString("gRando.InputSeed", data->Buf);
                             return 0;
                         });
        if (stringSeed.length() < 1) {
            ImGui::SameLine(17.0f);
            ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.4f), "Leave blank for random seed");
        }
        UIWidgets::PopStyleSlider();

        UIWidgets::CVarCheckbox("Generate Spoiler File", "gRando.GenerateSpoiler");
    }
    ImGui::SeparatorText("Enhancements");
    UIWidgets::CVarCheckbox("Container Style Matches Contents", "gRando.CSMC");
    UIWidgets::Tooltip("This will make the contents of a container match the container itself. This currently only "
                       "applies to chests and pots.");
    UIWidgets::WindowButton("Check Tracker", "gWindows.CheckTracker", BenGui::mRandoCheckTrackerWindow,
                            { .size = ImVec2((ImGui::GetContentRegionAvail().x - 48.0f), 40.0f) });
    ImGui::SameLine();
    if (UIWidgets::Button(ICON_FA_COG, { .size = ImVec2(40.0f, 40.0f) })) {
        BenGui::mRandoCheckTrackerSettingsWindow->ToggleVisibility();
    }
    ImGui::EndChild();
    ImGui::SameLine();
}

static void DrawLogicConditionsTab() {
    f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2);
    f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2);
    ImGui::BeginChild("randoLogicColumn1", ImVec2(columnWidth, halfHeight));
    if (UIWidgets::CVarCombobox("Logic", Rando::StaticData::Options[RO_LOGIC].cvar, &logicOptions)) {
        ClearIncompatibleSetting();
    }
    UIWidgets::Tooltip(
        "Glitchless - The items are shuffled in a way that guarantees the seed is beatable without "
        "glitches.\n\n"
        "No Logic - The items are shuffled completely randomly, this can result in unbeatable seeds, and "
        "will require heavy use of glitches.\n\n"
        "Nearly No Logic - The items are shuffled completely randomly, with the following exceptions:\n"
        "- Oath to Order and Remains cannot be placed on the Moon.\n"
        "- Deku Mask, Zora Mask, Sonata, and Bossa Nova cannot be placed in their respective Temples or on "
        "the Moon.\n\n"
        "Vanilla - The items are not shuffled.\n"
        "Not compatible with settings that add items to the pool, like Boss Souls or Plentiful Items.");
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("randoLogicColumn2", ImVec2(columnWidth, halfHeight));

    UIWidgets::CVarCombobox("Dungeon Access", Rando::StaticData::Options[RO_ACCESS_DUNGEONS].cvar,
                            &accessDungeonOptions);
    UIWidgets::Tooltip("Dungeon access requirements:\n\n"
                       "Requires Transformation & Song - Requires both the correct form and the song (Vanilla).\n\n"
                       "Requires Transformation or Song - Requires either the correct form or the song.\n\n"
                       "Requires Only Transformation - Requires only the correct form.\n\n"
                       "Requires Only Song - Requires only the correct song.\n\n"
                       "Open - Dungeons will be open with no requirements.");
    UIWidgets::CVarSliderInt("Majora Access Remains Required",
                             Rando::StaticData::Options[RO_ACCESS_MAJORA_REMAINS_COUNT].cvar,
                             IntSliderOptions().Min(0).Max(4).DefaultValue(0));
    UIWidgets::CVarSliderInt("Moon Access Remains Required",
                             Rando::StaticData::Options[RO_ACCESS_MOON_REMAINS_COUNT].cvar,
                             IntSliderOptions().Min(0).Max(4).DefaultValue(4));
    UIWidgets::CVarCombobox("Trials Access", Rando::StaticData::Options[RO_ACCESS_TRIALS].cvar, &accessTrialsOptions);
    ImGui::EndChild();
    ImGui::BeginChild("randoLogicTricks", ImVec2(0, 0));
    ImGui::SeparatorText("Tricks & Glitches");
    ImGui::EndChild();
}

static void DrawShufflesTab() {
    f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2);
    f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2);
    ImGui::SeparatorText("Shuffle Options");
    ImGui::BeginChild("randoShufflesColumn1", ImVec2(columnWidth, halfHeight));
    CVarCheckbox("Shuffle Songs", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }).DefaultValue(true));
    CVarCheckbox("Shuffle Owl Statues", Rando::StaticData::Options[RO_SHUFFLE_OWL_STATUES].cvar);
    CVarCheckbox("Shuffle Shops", Rando::StaticData::Options[RO_SHUFFLE_SHOPS].cvar);
    CVarCheckbox("Shuffle Tingle Maps", Rando::StaticData::Options[RO_SHUFFLE_TINGLE_SHOPS].cvar);
    CVarCheckbox("Shuffle Boss Remains", Rando::StaticData::Options[RO_SHUFFLE_BOSS_REMAINS].cvar);
    CVarCheckbox("Shuffle Cows", Rando::StaticData::Options[RO_SHUFFLE_COWS].cvar);
    CVarCheckbox("Shuffle Gold Skulltula Tokens", Rando::StaticData::Options[RO_SHUFFLE_GOLD_SKULLTULAS].cvar);
    CVarSliderInt("Minimum Required Gold Skulltula Tokens",
                  Rando::StaticData::Options[RO_MINIMUM_SKULLTULA_TOKENS].cvar,
                  IntSliderOptions(
                      { { .tooltip = "Minimum Gold Skulltula tokens needed to obtain the Spider House checks.",
                          .disabled = !CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_GOLD_SKULLTULAS].cvar, 0),
                          .disabledTooltip = "Only takes effect if Gold Skulltula Tokens are shuffled." } })
                      .Min(1)
                      .Max(SPIDER_HOUSE_TOKENS_REQUIRED)
                      .DefaultValue(SPIDER_HOUSE_TOKENS_REQUIRED));
    CVarSliderInt(
        "Minimum Required Stray Fairies", Rando::StaticData::Options[RO_MINIMUM_STRAY_FAIRIES].cvar,
        IntSliderOptions({ { .tooltip = "Minimum Stray Fairies needed to obtain the corresponding Great Fairy check.\n"
                                        "Does not affect the Clock Town fairy." } })
            .Min(1)
            .Max(STRAY_FAIRY_SCATTERED_TOTAL)
            .DefaultValue(STRAY_FAIRY_SCATTERED_TOTAL));
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("randoShufflesColumn2", ImVec2(columnWidth, halfHeight));
    CVarCheckbox("Shuffle Pot Drops", Rando::StaticData::Options[RO_SHUFFLE_POT_DROPS].cvar);
    CVarCheckbox("Shuffle Crate Drops", Rando::StaticData::Options[RO_SHUFFLE_CRATE_DROPS].cvar);
    CVarCheckbox("Shuffle Barrel Drops", Rando::StaticData::Options[RO_SHUFFLE_BARREL_DROPS].cvar);
    CVarCheckbox("Shuffle Snowball Drops", Rando::StaticData::Options[RO_SHUFFLE_SNOWBALL_DROPS].cvar);
    CVarCheckbox("Shuffle Grass Drops", Rando::StaticData::Options[RO_SHUFFLE_GRASS_DROPS].cvar);
    CVarCheckbox("Shuffle Frogs", Rando::StaticData::Options[RO_SHUFFLE_FROGS].cvar);
    CVarCheckbox("Shuffle Hive Drops", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Shuffle Freestanding Items", Rando::StaticData::Options[RO_SHUFFLE_FREESTANDING_ITEMS].cvar);
    CVarCheckbox("Shuffle Wonder Items", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("randoLocationsColumn3", ImVec2(columnWidth, halfHeight));
    CVarCheckbox("Triforce Hunt", Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar);
    ImGui::BeginDisabled(!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRIFORCE_PIECES].cvar, RO_GENERIC_OFF));
    CVarSliderInt(
        "Required Triforce Pieces", Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar,
        IntSliderOptions({})
            .Min(1)
            .Max(CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX))
            .DefaultValue(DEFAULT_TRIFORCE_PIECES_MAX));
    if (CVarSliderInt(
            "Shuffled Triforce Pieces", Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar,
            IntSliderOptions({})
                .Min(1)
                .Max(1000)
                .DefaultValue(DEFAULT_TRIFORCE_PIECES_MAX)
                .Tooltip("If the maximum amount of placeable pieces exceeds what will allow the seed to generate, the "
                         "amount will be adjusted automatically."))) {
        if (CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar, DEFAULT_TRIFORCE_PIECES_MAX) >
            CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX)) {
            CVarGetInteger(
                Rando::StaticData::Options[RO_TRIFORCE_PIECES_REQUIRED].cvar,
                CVarGetInteger(Rando::StaticData::Options[RO_TRIFORCE_PIECES_MAX].cvar, DEFAULT_TRIFORCE_PIECES_MAX));
        }
    }

    ImGui::EndDisabled();
    ImGui::EndChild();
}

static void DrawItemsTab() {
    f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2);
    ImGui::BeginChild("randoItemsColumn1", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
    CVarCheckbox("Shuffle Swim", Rando::StaticData::Options[RO_SHUFFLE_SWIM].cvar,
                 CheckboxOptions({ { .tooltip = "Shuffles the ability to Swim, entering the Swim state or submerging\n"
                                                "into deep water will respawn Link.",
                                     .disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_SWIM),
                                     .disabledTooltip = "Incompatible with current Logic Setting" } }));
    CVarCheckbox("Deku Stick Bag", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Deku Nut Bag", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Skeleton Key", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Child Wallet", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Infinite Upgrades", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Song of Double Time", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Inverted Song of Time", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Saria's Song", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Sun's Song", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("randoItemsColumn2", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
    CVarCheckbox(
        "Plentiful Items", Rando::StaticData::Options[RO_PLENTIFUL_ITEMS].cvar,
        CheckboxOptions({ { .tooltip = "Major items, masks, and keys will have an extra copy added to the item pool. \n"
                                       "Lesser items, stray fairies, and skulltula tokens will have a chance for an "
                                       "extra copy to be added to the item pool.",
                            .disabled = IncompatibleWithLogicSetting(RO_PLENTIFUL_ITEMS),
                            .disabledTooltip = "Incompatible with current Logic Setting" } }));
    CVarCheckbox(
        "Boss Souls", Rando::StaticData::Options[RO_SHUFFLE_BOSS_SOULS].cvar,
        CheckboxOptions({ { .tooltip = "Adds the \"souls\" of the five bosses to the item pool. Boss Souls are items "
                                       "that must be found in order for their corresponding boss to spawn.",
                            .disabled = IncompatibleWithLogicSetting(RO_SHUFFLE_BOSS_SOULS),
                            .disabledTooltip = "Incompatible with current Logic Setting" } }));
    CVarCheckbox("Enemy Drops", Rando::StaticData::Options[RO_SHUFFLE_ENEMY_DROPS].cvar,
                 CheckboxOptions({ { .tooltip = "Shuffles the first drop from a non Boss Enemy." } }));
    CVarCheckbox("Enemy Souls", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("randoItemsColumn3", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
    CVarCheckbox("Shuffle Traps", Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar,
                 CheckboxOptions({ { .tooltip = "Ice Trap time!" } }));
    CVarSliderInt(
        "##trapcount", Rando::StaticData::Options[RO_TRAP_AMOUNT].cvar,
        IntSliderOptions({ { .tooltip = "How many Traps are shuffled into the Item Pool.",
                             .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                             .disabledTooltip = "Shuffle Traps is disabled." } })
            .LabelPosition(LabelPosition::None)
            .Color(UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", 5)))
            .Format("Traps: %i")
            .Min(1)
            .Max(10)
            .DefaultValue(5));
    ImGui::SeparatorText("Toggle Trap Types");
    CVarCheckbox(
        "Freeze Traps", "gRando.Traps.Freeze",
        CheckboxOptions({ { .tooltip = "Freezes Link in place.",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    CVarCheckbox(
        "Blast Traps", "gRando.Traps.Blast",
        CheckboxOptions({ { .tooltip = "Link explodes with Powder Keg force.",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    CVarCheckbox(
        "Shock Traps", "gRando.Traps.Shock",
        CheckboxOptions({ { .tooltip = "Shocks Link for a few seconds.",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    CVarCheckbox(
        "Jinx Traps", "gRando.Traps.Jinx",
        CheckboxOptions({ { .tooltip = "Afflicts Link with Jinx.",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    CVarCheckbox(
        "Wallet Traps", "gRando.Traps.Wallet",
        CheckboxOptions({ { .tooltip = "Links rupees scatter around him.",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    CVarCheckbox( // This only spawns a Like Like, more enemies may be added in the future but each would need fine
                  // tuning
        "Like Like Traps", "gRando.Traps.Enemy",
        CheckboxOptions({ { .tooltip = "Spawns a Like Like on top of Link.",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    CVarCheckbox(
        "Time Traps", "gRando.Traps.Time",
        CheckboxOptions({ { .tooltip = "Advances Time 90 Minutes (Game Time).",
                            .disabled = (bool)!CVarGetInteger(Rando::StaticData::Options[RO_SHUFFLE_TRAPS].cvar, 0),
                            .disabledTooltip = "Shuffle Traps is disabled." } }));
    ImGui::EndChild();
}

static void DrawStartingItemsTab() {
    f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2);
    f32 quarterHeight = ImGui::GetContentRegionAvail().y / 4 - (ImGui::GetStyle().ItemSpacing.y * 4);
    int tableColumns = 0;
    ImGui::BeginChild("randoStartingOptions", ImVec2(0, quarterHeight));
    ImGui::SeparatorText("Starting Options");
    if (ImGui::BeginTable("Starting Options", 3)) {
        ImGui::TableNextColumn();
        CVarCheckbox("Wallet Full", Rando::StaticData::Options[RO_STARTING_RUPEES].cvar,
                     CheckboxOptions({ {
                         .tooltip = "Start with a full wallet",
                     } }));

        ImGui::TableNextColumn();
        CVarCheckbox("Consumables Full", Rando::StaticData::Options[RO_STARTING_CONSUMABLES].cvar,
                     CheckboxOptions({ {
                         .tooltip = "Start with full Deku Sticks and Deku Nuts",
                     } }));

        ImGui::TableNextColumn();
        CVarCheckbox("Maps and Compasses", Rando::StaticData::Options[RO_STARTING_MAPS_AND_COMPASSES].cvar,
                     CheckboxOptions({ {
                         .tooltip = "Enables maps and compasses everywhere",
                     } }));

        ImGui::TableNextColumn();
        CVarSliderInt("Health", Rando::StaticData::Options[RO_STARTING_HEALTH].cvar,
                      IntSliderOptions()
                          .Min(1)
                          .Max(20)
                          .DefaultValue(3)
                          .LabelPosition(LabelPosition::None)
                          .Format("%d Hearts")
                          .Color(Colors::Red));

        ImGui::EndTable();
    }
    ImGui::EndChild();
    ImGui::BeginChild("randoStartingItems1", ImVec2(0, quarterHeight));
    ImGui::SeparatorText("Starting Items");
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
    ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(15, 15));
    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
    ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
    ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));

    std::vector<RandoItemId> setStartingItemsList =
        convertStartingItemsToRandoItemId(CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT), ",");
    uint32_t listIndex = 0;
    for (auto& startingItem : setStartingItemsList) {
        ImGui::PushID(listIndex);
        ImVec2 imageSize = ImVec2(42.0f, 42.0f);
        if ((startingItem >= RI_SONG_ELEGY && startingItem <= RI_SONG_TIME) || startingItem == RI_PROGRESSIVE_LULLABY) {
            imageSize.x /= 1.5f;
        }

        Rando::StaticData::RandoStaticItem randoStaticItem = Rando::StaticData::Items[startingItem];
        const char* texturePath = Rando::StaticData::GetIconTexturePath(startingItem);
        ImTextureID textureId = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(texturePath);

        if (ImGui::ImageButton(
                std::to_string(listIndex).c_str(), textureId, imageSize, ImVec2(0, 0), ImVec2(1, 1), ImVec4(0, 0, 0, 0),
                Ship_GetItemColorTint(startingItem == RI_PROGRESSIVE_LULLABY ? ITEM_SONG_LULLABY
                                                                             : randoStaticItem.itemId))) {
            for (size_t i = 0; i < setStartingItemsList.size(); i++) {
                if (setStartingItemsList[i] == startingItem) {
                    setStartingItemsList.erase(setStartingItemsList.begin() + i);
                    break;
                }
            }
            CVarSetString("gRando.StartingItems", CreateStartingItemsToCvar(setStartingItemsList).c_str());
            Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
        }
        UIWidgets::Tooltip(randoStaticItem.name);
        listIndex++;

        if ((listIndex + 1) % 15 != 0) {
            ImGui::SameLine();
        }
        ImGui::PopID();
    }

    ImGui::PopStyleColor(3);
    ImGui::PopStyleVar(2);

    ImGui::EndChild();
    ImGui::BeginChild("randoStartingItems2", ImVec2(0, 0));
    ImGui::SeparatorText("Available Items");

    for (auto& category : Rando::StaticData::StartingItemsMap) {
        tableColumns = 5;
        if (category.first == STARTING_ITEMS_MASK) {
            tableColumns++;
        }
        ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0));
        if (ImGui::BeginChild(std::to_string(category.first).c_str(), ImVec2(0, 0),
                              ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX |
                                  ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Borders)) {
            ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
            ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
            ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
            ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
            ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f));

            if (ImGui::BeginTable(std::to_string(category.first).c_str(), tableColumns)) {
                for (int i = 0; i < tableColumns; i++) {
                    ImGui::TableSetupColumn("item", ImGuiTableColumnFlags_WidthFixed, 50.0f);
                }
                for (auto& item : category.second) {
                    ImVec2 imageSize = ImVec2(42.0f, 42.0f);
                    if ((item >= RI_SONG_ELEGY && item <= RI_SONG_TIME) || item == RI_PROGRESSIVE_LULLABY) {
                        imageSize.x /= 1.5f;
                    }

                    Rando::StaticData::RandoStaticItem randoStaticItem = Rando::StaticData::Items[item];
                    const char* texturePath = Rando::StaticData::GetIconTexturePath(item);
                    ImTextureID textureId =
                        Ship::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName(texturePath);

                    if (item == RI_SONG_TIME) {
                        ImGui::TableNextRow();
                    }
                    ImGui::TableNextColumn();
                    if (ImGui::ImageButton(std::to_string(item).c_str(), textureId, imageSize, ImVec2(0, 0),
                                           ImVec2(1, 1), ImVec4(0, 0, 0, 0),
                                           Ship_GetItemColorTint(item == RI_PROGRESSIVE_LULLABY
                                                                     ? ITEM_SONG_LULLABY
                                                                     : randoStaticItem.itemId))) {
                        std::string currentStartingItems =
                            CVarGetString("gRando.StartingItems", RANDO_STARTING_ITEMS_DEFAULT);
                        if (currentStartingItems.length() != 0) {
                            currentStartingItems += ",";
                        }
                        currentStartingItems += std::to_string(item);
                        CVarSetString("gRando.StartingItems", currentStartingItems.c_str());
                        Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
                    }
                    UIWidgets::Tooltip(randoStaticItem.name);
                }
                ImGui::EndTable();
            }

            ImGui::PopStyleColor(3);
            ImGui::PopStyleVar(2);
            ImGui::EndChild();
        }
        ImGui::PopStyleColor(1);
        if (category.first != STARTING_ITEMS_QUEST) {
            ImGui::SameLine();
        }
    }
    ImGui::EndChild();
}

static f32 CalcButtonWidth(const char* label) {
    const auto& style = ImGui::GetStyle();
    f32 buttonPaddingX = style.FramePadding.x;
    f32 buttonBorderAndSpacing = style.FrameBorderSize + style.ItemSpacing.x * 0.5f;
    return ImGui::CalcTextSize(label).x + (buttonPaddingX * 2) + buttonBorderAndSpacing;
}

static void DrawFilterWithButton(ImGuiTextFilter& filter, const char* placeholderText, const char* buttonLabel,
                                 f32 availableWidth, UIWidgets::Colors menuThemeColor, bool& actionFlagWhenFiltered,
                                 bool& actionFlagWhenAll, f32 filterButtonSpacing, f32 filterButtonOffset,
                                 f32 filterButtonPaddingY, f32 filterSearchLabelOffset) {
    UIWidgets::PushStyleCombobox(menuThemeColor);

    f32 buttonWidth = CalcButtonWidth(buttonLabel);
    filter.Draw("##filter", availableWidth - buttonWidth - filterButtonSpacing);
    if (!filter.IsActive()) {
        ImGui::SameLine(filterSearchLabelOffset);
        ImGui::Text("%s", placeholderText);
    }
    UIWidgets::PopStyleCombobox();

    // SameLine() uses absolute positioning from line start
    ImGui::SameLine(availableWidth - buttonWidth - filterButtonOffset);
    if (UIWidgets::Button(buttonLabel, { .padding = ImVec2(ImGui::GetStyle().FramePadding.x, filterButtonPaddingY),
                                         .color = menuThemeColor })) {
        if (filter.IsActive()) {
            actionFlagWhenFiltered = true;
        } else {
            actionFlagWhenAll = true;
        }
    }
}

static void DrawCheckFilterTab() {
    if (CVarGetInteger(Rando::StaticData::Options[RO_LOGIC].cvar, RO_LOGIC_GLITCHLESS) >= RO_LOGIC_VANILLA) {
        ImGui::TextColored(UIWidgets::ColorValues.at(UIWidgets::Colors::Red),
                           "This setting is not compatible with Vanilla Logic.");
        return;
    }

    auto menuThemeColor = UIWidgets::Colors(CVarGetInteger("gSettings.Menu.Theme", LightBlue));
    bool excludeAllChecks = false;
    bool excludeFiltered = false;
    bool removeFiltered = false;
    bool removeAllChecks = false;
    if (!isExcludedInitialized) {
        LoadExcludedChecks();
        isExcludedInitialized = true;
    }

    f32 columnWidth = ImGui::GetContentRegionAvail().x / 2 - (ImGui::GetStyle().ItemSpacing.x * 2);
    ImGui::BeginChild("randoIncludedChecks", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
    ImGui::SeparatorText("Normal Checks");

    static ImGuiTextFilter includedFilter;
    const char* leftButtonLabel = includedFilter.IsActive() ? "Junk Filtered" : "Junk All";
    f32 availableWidth = ImGui::GetContentRegionAvail().x;
    DrawFilterWithButton(includedFilter, "Normal Search", leftButtonLabel, availableWidth, menuThemeColor,
                         excludeFiltered, excludeAllChecks, 30.0f, 24.0f, 6.0f, 18.0f);

    if (ImGui::BeginTable("Normal Checks", 1)) {
        ImGui::TableNextColumn();

        for (auto& includedChecks : Rando::StaticData::Checks) {
            if (includedChecks.first == RC_UNKNOWN) {
                continue;
            }

            if (!includedFilter.PassFilter(Rando::StaticData::CheckNames[includedChecks.first].c_str())) {
                continue;
            }

            if (std::binary_search(checkExclusionList.begin(), checkExclusionList.end(), includedChecks.first)) {
                continue;
            }

            ImGui::BeginGroup();
            ImGui::Text("%s", Rando::StaticData::CheckNames[includedChecks.first].c_str());
            ImGui::SameLine();
            ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x, 0));
            ImGui::EndGroup();

            ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
                                   ImGui::IsItemHovered() ? IM_COL32(255, 255, 0, 128) : IM_COL32(255, 255, 255, 0));
            if (ImGui::IsItemClicked()) {
                auto it = std::lower_bound(checkExclusionList.begin(), checkExclusionList.end(), includedChecks.first);
                if (it == checkExclusionList.end() || *it != includedChecks.first) {
                    checkExclusionList.insert(it, includedChecks.first);
                    SaveExcludedChecks();
                }
            }
            ImGui::TableNextColumn();
        }
        ImGui::EndTable();
    }
    ImGui::EndChild();
    ImGui::SameLine();
    ImGui::BeginChild("randoExcludedChecks", ImVec2(columnWidth, ImGui::GetContentRegionAvail().y));
    ImGui::SeparatorText("Forced Junk Checks");

    static ImGuiTextFilter excludedFilter;
    const char* rightButtonLabel = excludedFilter.IsActive() ? "Remove Filtered" : "Remove All";
    f32 rightAvailableWidth = ImGui::GetContentRegionAvail().x;
    DrawFilterWithButton(excludedFilter, "Junk Search", rightButtonLabel, rightAvailableWidth, menuThemeColor,
                         removeFiltered, removeAllChecks, 30.0f, 24.0f, 6.0f, 18.0f);

    if (ImGui::BeginTable("Forced Junk Checks", 1)) {
        ImGui::TableNextColumn();
        for (auto it = checkExclusionList.begin(); it != checkExclusionList.end();) {
            if (!excludedFilter.PassFilter(Rando::StaticData::CheckNames[*it].c_str())) {
                ++it;
                continue;
            }

            ImGui::Text("%s", Rando::StaticData::CheckNames[*it].c_str());

            ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
                                   ImGui::IsItemHovered() ? IM_COL32(255, 255, 0, 128) : IM_COL32(255, 255, 255, 0));
            if (ImGui::IsItemClicked()) {
                it = checkExclusionList.erase(it);
                SaveExcludedChecks();
            } else {
                ++it;
            }
            ImGui::TableNextColumn();
        }
        ImGui::EndTable();
    }
    ImGui::EndChild();

    // Junk all checks
    if (excludeAllChecks) {
        checkExclusionList.clear();
        checkExclusionList.reserve(Rando::StaticData::Checks.size());
        for (auto& includedChecks : Rando::StaticData::Checks) {
            if (includedChecks.first != RC_UNKNOWN) {
                checkExclusionList.push_back(includedChecks.first);
            }
        }
        SortExcludedChecks();
        SaveExcludedChecks();
        includedFilter.Clear();
        excludeAllChecks = false;
    }

    // Junk filtered checks
    if (excludeFiltered) {
        for (auto& includedChecks : Rando::StaticData::Checks) {
            if (includedChecks.first == RC_UNKNOWN) {
                continue;
            }

            if (!includedFilter.PassFilter(Rando::StaticData::CheckNames[includedChecks.first].c_str())) {
                continue;
            }

            auto it = std::lower_bound(checkExclusionList.begin(), checkExclusionList.end(), includedChecks.first);
            if (it == checkExclusionList.end() || *it != includedChecks.first) {
                checkExclusionList.insert(it, includedChecks.first);
            }
        }
        SaveExcludedChecks();
        includedFilter.Clear();
        excludeFiltered = false;
    }

    // Remove filtered checks: erase_if removes items where PassFilter returns true
    if (removeFiltered) {
        std::erase_if(checkExclusionList, [&](const RandoCheckId& checkId) {
            return excludedFilter.PassFilter(Rando::StaticData::CheckNames[checkId].c_str());
        });
        SaveExcludedChecks();
        excludedFilter.Clear();
        removeFiltered = false;
    }

    // Remove all checks
    if (removeAllChecks) {
        checkExclusionList.clear();
        SaveExcludedChecks();
        excludedFilter.Clear();
        removeAllChecks = false;
    }
}

static void DrawHintsTab() {
    f32 columnWidth = ImGui::GetContentRegionAvail().x / 3 - (ImGui::GetStyle().ItemSpacing.x * 2);
    f32 halfHeight = ImGui::GetContentRegionAvail().y / 2 - (ImGui::GetStyle().ItemSpacing.y * 2);
    ImGui::BeginChild("randoHintsColumn1", ImVec2(columnWidth, halfHeight));
    CVarCheckbox(
        "Spider House", Rando::StaticData::Options[RO_HINTS_SPIDER_HOUSES].cvar,
        CheckboxOptions(
            { { .tooltip =
                    "Swamp Spider House: Hinted at his normal location within the Swamp Spider House\n\nOcean Spider "
                    "House: Hinted in South Clock Town day 1, by the main standing on the scaffolding." } }));
    CVarCheckbox(
        "Gossip Stone Static Hint", Rando::StaticData::Options[RO_HINTS_GOSSIP_STONES].cvar,
        CheckboxOptions(
            { { .tooltip = "Each gossip stone will give a static hint about the contents of a random location." } }));
    CVarCheckbox(
        "Gossip Stone Purchaseable", Rando::StaticData::Options[RO_HINTS_PURCHASEABLE].cvar,
        CheckboxOptions({ { .tooltip = "Gossip stones will offer a hint for a scaling rupee cost. This cost ranges "
                                       "from 10-250 rupees depending on how many checks are remaining in your seed. "
                                       "The hint will guaranteed be a check you have not obtained yet." } }));
    CVarCheckbox(
        "Boss Remains", Rando::StaticData::Options[RO_HINTS_BOSS_REMAINS].cvar,
        CheckboxOptions(
            { { .tooltip =
                    "Lists the location of the Boss remains on the guard recruitment posters around Clock Town" } }));
    CVarCheckbox("Oath to Order", Rando::StaticData::Options[RO_HINTS_OATH_TO_ORDER].cvar,
                 CheckboxOptions({ { .tooltip = "Once you have the Moon Access Requirements, talking to Skull Kid on "
                                                "the Clock Tower Rooftop will hint the location of Oath to Order" } }));
    CVarCheckbox(
        "General Actor Hints", "gPlaceholderBool",
        CheckboxOptions({ { .disabled = true,
                            .disabledTooltip = "Soon you will be able to disable these. Currently hinted:\n- Bomb Shop "
                                               "4th Item\n- Lottery\n- Great Fairy Fountains\n- Mountain Smithy" } })
            .DefaultValue(true));
    CVarCheckbox("Saria's Song", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox("Song of Soaring", "gPlaceholderBool",
                 CheckboxOptions({ { .disabled = true, .disabledTooltip = "Coming Soon" } }));
    CVarCheckbox(
        "Hookshot Location", Rando::StaticData::Options[RO_HINTS_HOOKSHOT].cvar,
        CheckboxOptions(
            { { .tooltip =
                    "The Zora in Great Bay Coast, near Pirates Fortress, will hint the location of the Hookshot." } }));
    ImGui::EndChild();
}

void Rando::RegisterMenu() {
    mBenMenu->AddMenuEntry("Rando", "gSettings.Menu.RandoSidebarSection");
    mBenMenu->AddSidebarEntry("Rando", "General", 1);
    WidgetPath path = { "Rando", "General", SECTION_COLUMN_1 };
    mBenMenu->AddWidget(path, "General", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawGeneralTab(); });
    mBenMenu->AddSidebarEntry("Rando", "Logic/Conditions", 1);
    path.sidebarName = "Logic/Conditions";
    mBenMenu->AddWidget(path, "Logic/Conditions", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
        DrawLogicConditionsTab();
    });
    mBenMenu->AddSidebarEntry("Rando", "Shuffle Options", 1);
    path.sidebarName = "Shuffle Options";
    mBenMenu->AddWidget(path, "Shuffle Options", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
        DrawShufflesTab();
    });
    mBenMenu->AddSidebarEntry("Rando", "Check Filter", 1);
    path.sidebarName = "Check Filter";
    mBenMenu->AddWidget(path, "Check Filter", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
        DrawCheckFilterTab();
    });
    mBenMenu->AddSidebarEntry("Rando", "Items", 1);
    path.sidebarName = "Items";
    mBenMenu->AddWidget(path, "Items", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawItemsTab(); });
    mBenMenu->AddSidebarEntry("Rando", "Starting Items", 1);
    path.sidebarName = "Starting Items";
    mBenMenu->AddWidget(path, "Starting Items", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) {
        DrawStartingItemsTab();
    });
    mBenMenu->AddSidebarEntry("Rando", "Hints", 1);
    path.sidebarName = "Hints";
    mBenMenu->AddWidget(path, "Hints", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { DrawHintsTab(); });
}

static RegisterMenuInitFunc initFunc(Rando::RegisterMenu);
